package movepaint.stateMachine;

import fr.lri.swingstates.canvas.CEllipse;
import fr.lri.swingstates.canvas.CPolyLine;
import fr.lri.swingstates.canvas.CShape;
import fr.lri.swingstates.canvas.CStateMachine;
import fr.lri.swingstates.canvas.CText;
import fr.lri.swingstates.canvas.Canvas;
import fr.lri.swingstates.sm.State;
import fr.lri.swingstates.sm.Transition;
import fr.lri.swingstates.sm.transitions.Press;
import fr.lri.swingstates.sm.transitions.Release;
import fr.lri.swingstates.sm.transitions.TimeOut;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.geom.Point2D;
import javax.swing.JColorChooser;
import movepaint.GestureRecognition;
import movepaint.stateMachine.InkMachine;
import movepaint.utils.ColorTag;
import movepaint.utils.MenuItem;

/**
 * Machine a état utilisée pour le menu circulaire
 * @author Paulino A.
 * @version 1.0
 */
public class PieMenuMachine extends CStateMachine {
    private Canvas _canvas;                 // Référence au canvas de la frame
    private GestureRecognition _frameMain;  // Référence à la frame principale
    private CShape selectedShape = null;

    // Les différentes actions proposées par les menus
    private static final int MODE_FILL_BACKGROUND   = 13;
    private static final int MODE_FILL_OUTLINED     = 14;
    private static final int MODE_COPY              = 16;
    private static final int MODE_PASTE             = 17;
    private static final int MODE_DELETE            = 18;
    private static final int MODE_ABOVE             = 19;
    private static final int MODE_ABOVE_ALL         = 20;
    private static final int MODE_BELOW             = 21;
    private static final int MODE_BELOW_ALL         = 22;


    // TAG pour la shape copiée
    public static final String TAG_SHAPE_COPIED = "TSC";
    // TAG pour le pieMenu des couleurs de fond
    public static final String TAG_PIE_MENU_BACKGROUND = "TPMB";
    // TAG pour le pieMenu des couleurs des bordures des figures
    public static final String TAG_PIE_MENU_OUTLINE = "TPMO";
    // TAG pour le pieMenu des actions possibles (copier, coller, supprimer)
    public static final String TAG_PIE_LABEL_EDIT = "TPLE";
    public static final String TAG_PIE_MENU_EDIT = "TPME";
    public static final String TAG_WHOLE_PIE_MENU_EDIT = "TWPME";
    // TAG pour le pieMenu des positions (devant/derriere)
    public static final String TAG_PIE_LABEL_POSITION = "TPLP";
    public static final String TAG_PIE_MENU_POSITION = "TPMP";
    public static final String TAG_WHOLE_PIE_MENU_POSITION = "TWPMP";

    // TAG pour le menu des choix circulaire
    public static final String TAG_PIE_LABEL_CHOICE = "TPLC";
    public static final String TAG_PIE_MENU_CHOICE = "TPMC";
    public static final String TAG_WHOLE_PIE_MENU_CHOICE = "TWPMC";

    // Elements composant les menus
    private String ITEM_BACKGROUND  = "Fond";
    private String ITEM_OUTLINE     = "Bordure";
    private String ITEM_EDIT        = "Edition";
    private String ITEM_COPY        = "Copier";
    private String ITEM_PASTE       = "Coller";
    private String ITEM_DELETE      = "Supprimer";
    private String ITEM_POSITION    = "Position";
    private String ITEM_ABOVE       = "Devant";
    private String ITEM_BELOW       = "Derrière";
    private String ITEM_ABOVE_ALL   = "Devant tout";
    private String ITEM_BELOW_ALL   = "Derrière tout";

    private String[] itemsMenu      = {ITEM_BACKGROUND, ITEM_OUTLINE, ITEM_POSITION, ITEM_EDIT};
    private String[] itemsActions   = {ITEM_COPY, ITEM_PASTE, ITEM_DELETE, ""};
    private String[] itemsPositions = {ITEM_ABOVE, ITEM_ABOVE_ALL, ITEM_BELOW, ITEM_BELOW_ALL};

    public PieMenuMachine(Canvas c, GestureRecognition frameMain) {
        super(c);
        _canvas = c;
        _frameMain = frameMain;

        // On créer les menus
        pieMenu(30, _canvas, TAG_PIE_MENU_BACKGROUND);
        pieMenu(30, _canvas, TAG_PIE_MENU_OUTLINE);
        choiceMenuLayout(itemsMenu, TAG_PIE_MENU_CHOICE, TAG_PIE_LABEL_CHOICE, TAG_WHOLE_PIE_MENU_CHOICE);
        choiceMenuLayout(itemsActions, TAG_PIE_MENU_EDIT, TAG_PIE_LABEL_EDIT, TAG_WHOLE_PIE_MENU_EDIT);
        choiceMenuLayout(itemsPositions, TAG_PIE_MENU_POSITION, TAG_PIE_LABEL_POSITION, TAG_WHOLE_PIE_MENU_POSITION);
        hideMenu(TAG_PIE_MENU_CHOICE);
        hideMenu(TAG_PIE_MENU_EDIT);
        hideMenu(TAG_PIE_MENU_POSITION);
    }

    /**
     * Retourne le nom de la machine a état
     * @return Nom de la machine a état
     */
    public String getName() {
        return "PieMenuMachine";
    }


    /**
     * Etat initial du mode selection/deplacement de figure
     */
    public State start = new State() {

        // Pour le menu de choix des couleurs, mise en place, etc.
        Transition menu = new PressOnTag(InkMachine.TAG_SHAPE, BUTTON3, ">> menuOn") {

            public void action() {
                selectedShape = getShape();
                showChoiceMenu(getPoint(), TAG_WHOLE_PIE_MENU_CHOICE, TAG_PIE_MENU_CHOICE, TAG_PIE_LABEL_CHOICE);
            }
        };

        // Pour le menu de choix des actions (supprimer, copier, coller) dans le "vide"
        Transition menuAction = new Press(BUTTON3, ">> menuOn") {

            public void action() {
                showChoiceMenu(getPoint(), TAG_WHOLE_PIE_MENU_EDIT, TAG_PIE_MENU_EDIT, TAG_PIE_LABEL_EDIT);
            }
        };
    };

    /**
     * Machine a etat du menu circulaire
     */
    public State menuOn = new State() {
        // mode actuel (Background, Outline, Stroke, etc..
        int mode;

        Color color = null;

        // Transition récupérant la couleur dans laquelle l'user se situe
        Transition enterColorMenu = new EnterOnTag(ColorTag.class) {
            public void action() {
                color = ((ColorTag) getTag()).getColor();
            }
        };

        // Menu de choix circulaire
        Transition enterMenuChoice = new EnterOnTag(MenuItem.class) {

            public void action() {
                String lastItemVisited = ((MenuItem) getTag()).getName();

                // On affiche le menu associe
                if (lastItemVisited.equalsIgnoreCase(ITEM_BACKGROUND)) {
                    // MAJ du mode
                    mode = MODE_FILL_BACKGROUND;

                    // On cache les autres menus
                    _frameMain._statusBar.isWork(false);
                    hideMenu(TAG_PIE_MENU_OUTLINE);
                    hideMenu(TAG_PIE_MENU_EDIT);
                    hideMenu(TAG_PIE_MENU_POSITION);

                    // Astuce!
                    _frameMain._statusBar.isWork(true);
                    _frameMain._statusBar.setStatusText("Tip : Clique gauche sur une part pour personnaliser une couleur...");

                    // On affiche le menu de sélection des couleurs de fond
                    Point2D pt = getPoint();
                    pt.setLocation(getPoint().getX() + 40, getPoint().getY() + 40);
                    showMenu(pt, TAG_PIE_MENU_BACKGROUND);
                } else if (lastItemVisited.equalsIgnoreCase(ITEM_OUTLINE)){
                    // MAJ du mode
                    mode = MODE_FILL_OUTLINED;

                    // On cache les autres menus
                    _frameMain._statusBar.isWork(false);
                    hideMenu(TAG_PIE_MENU_BACKGROUND);
                    hideMenu(TAG_PIE_MENU_EDIT);
                    hideMenu(TAG_PIE_MENU_POSITION);

                    // Astuce!
                    _frameMain._statusBar.isWork(true);
                    _frameMain._statusBar.setStatusText("Tip : Clique gauche sur une part pour personnaliser une couleur...");

                    // On affiche le menu de sélection des couleurs de fond
                    Point2D pt = getPoint();
                    pt.setLocation(getPoint().getX() - 40, getPoint().getY() + 40);
                    showMenu(pt, TAG_PIE_MENU_OUTLINE);
                } else if (lastItemVisited.equalsIgnoreCase(ITEM_EDIT)){
                    // On cache les autres menus
                    _frameMain._statusBar.isWork(false);
                    hideMenu(TAG_PIE_MENU_BACKGROUND);
                    hideMenu(TAG_PIE_MENU_OUTLINE);
                    hideMenu(TAG_PIE_MENU_POSITION);

                    // On affiche le menu de sélection actions disponibles
                    Point2D pt = getPoint();
                    pt.setLocation(getPoint().getX() + 50, getPoint().getY() - 50);
                    showChoiceMenu(pt, TAG_WHOLE_PIE_MENU_EDIT, TAG_PIE_MENU_EDIT, TAG_PIE_LABEL_EDIT);
                } else if (lastItemVisited.equalsIgnoreCase(ITEM_POSITION)){
                    // On cache les autres menus
                    _frameMain._statusBar.isWork(false);
                    hideMenu(TAG_PIE_MENU_BACKGROUND);
                    hideMenu(TAG_PIE_MENU_OUTLINE);
                    hideMenu(TAG_PIE_MENU_EDIT);

                    // On affiche le menu de sélection actions disponibles
                    Point2D pt = getPoint();
                    pt.setLocation(getPoint().getX() - 50, getPoint().getY() - 50);
                    showChoiceMenu(pt, TAG_WHOLE_PIE_MENU_POSITION, TAG_PIE_MENU_POSITION, TAG_PIE_LABEL_POSITION);
                } else if (lastItemVisited.equalsIgnoreCase(ITEM_COPY)){
                    mode = MODE_COPY;
                } else if (lastItemVisited.equalsIgnoreCase(ITEM_PASTE)){
                    mode = MODE_PASTE;
                } else if (lastItemVisited.equalsIgnoreCase(ITEM_DELETE)){
                    mode = MODE_DELETE;
                } else if (lastItemVisited.equalsIgnoreCase(ITEM_ABOVE)){
                    mode = MODE_ABOVE;
                } else if (lastItemVisited.equalsIgnoreCase(ITEM_ABOVE_ALL)){
                    mode = MODE_ABOVE_ALL;
                } else if (lastItemVisited.equalsIgnoreCase(ITEM_BELOW)){
                    mode = MODE_BELOW;
                } else if (lastItemVisited.equalsIgnoreCase(ITEM_BELOW_ALL)){
                    mode = MODE_BELOW_ALL;
                }
            }
        };

        // Enter on menu center
        Transition hideBackgroundMenu = new LeaveOnTag(TAG_PIE_MENU_BACKGROUND) {
            public void action() {
                color = null;
            }
        };
        Transition hideOutlinedMenu = new LeaveOnTag(TAG_PIE_MENU_OUTLINE) {
            public void action() {
                color = null;
            }
        };

        // Transition appliquant la couleur sélectionnée par l'user dans la shape associée
        Transition selectColor = new ReleaseOnTag(ColorTag.class, BUTTON3, ">> menuOff") {
            public void action() {
                armTimer(100, false);
                if (color != null && selectedShape != null) {
                    if (mode == MODE_FILL_BACKGROUND)       // Remplissage de la figure
                        selectedShape.setFillPaint(color);
                    else if (mode == MODE_FILL_OUTLINED)    // Modification de la couleur des bords de la figure
                        selectedShape.setOutlinePaint(color);

                    // On réinitialise les anciennes couleurs et shape sélectionnée
                    color = null;
                    selectedShape = null;
                }
            }
        };

        // Transition appliquant l'action choisie par l'user (copier, coller, supprimer, etc.)
        Transition selectAction = new ReleaseOnTag(MenuItem.class, BUTTON3, ">> menuOff") {

            public void action() {
                armTimer(100, false);
                if (mode == MODE_COPY) {
                    // On copie le shapesi elle existe
                    _frameMain._statusBar.isWork(true);

                    if (selectedShape != null) {
                        selectedShape.setStroke(new BasicStroke(InkMachine.SHAPE_HOVERED));
                        selectedShape.addTag(TAG_SHAPE_COPIED);

                        _frameMain._statusBar.setStatusText("La figure a été copiée!");
                    } else {
                        _frameMain._statusBar.setStatusText("Cliquez sur une figure pour pouvoir la copier!!");
                    }

                    _frameMain._statusBar.isWork(false);
                } else if (mode == MODE_PASTE) {
                    // On colle la shape copiée
                    _frameMain._statusBar.isWork(true);

                    // On check qu'il existe une shape copiée
                    if (_frameMain.canvasHasTag(TAG_SHAPE_COPIED)) {

                        // On colle la shape
                        CShape shapeCopied = _canvas.getTag(TAG_SHAPE_COPIED).getTopMostShape().duplicate();
                        shapeCopied.translateTo(getPoint().getX(), getPoint().getY()).setDrawable(true).addTag(InkMachine.TAG_SHAPE).setStroke(new BasicStroke(InkMachine.SHAPE_NOT_HOVERED));

                        // On surligne la shape d'origine
                        selectedShape.setStroke(new BasicStroke(InkMachine.SHAPE_HOVERED));
                        // On vire le tag pour la copie
                        selectedShape.removeTag(TAG_SHAPE_COPIED);
                        // On réinitialise les shapes
                        selectedShape = shapeCopied = null;

                        _frameMain._statusBar.setStatusText("La figure a été collée!");
                    } else {
                        _frameMain._statusBar.setStatusText("Aucune figure n'a été copiée au préalable!!");
                    }

                    
                    _frameMain._statusBar.isWork(false);
                } else if (mode == MODE_DELETE) {
                    _frameMain._statusBar.isWork(true);
                    // On supprime la shape sélectionnée
                    if (selectedShape != null) {
                        selectedShape.addTag(InkMachine.TAG_SHAPE_HOVERED);
                        _frameMain.deleteShapes(InkMachine.TAG_SHAPE_HOVERED);

                        _frameMain._statusBar.setStatusText("La figure a été supprimée!");
                    } else {
                        _frameMain._statusBar.setStatusText("Aucune figure n'a été sélectionnée!");
                    }

                    _frameMain._statusBar.isWork(false);
                } else if (mode == MODE_ABOVE) {
                    _frameMain._statusBar.isWork(true);

                    // On change la position de la figure
                    CShape shapeJustAbove = null;
                    boolean found = false;
                    for (CShape shape:_canvas.getDisplayList()) {
                        // On récupère la premiere shape au dessus
                        if (shape.hasTag(InkMachine.TAG_SHAPE)   &&
                                        (shape != selectedShape) &&
                                        shape.isAbove(selectedShape) && !found) {
                            shapeJustAbove = shape;
                            found = true;
                        }
                    }

                    // MAJ s'il existe une shape au dessus de celle courante
                    if (shapeJustAbove != null)
                       selectedShape.above(shapeJustAbove);

                    _frameMain._statusBar.setStatusText("La figure a été placée au dessus de celle qui la précède!");
                    _frameMain._statusBar.isWork(false);
                } else if (mode == MODE_ABOVE_ALL) {
                    _frameMain._statusBar.isWork(true);
                    // On change la position de la figure
                    selectedShape.aboveAll();

                    _frameMain._statusBar.setStatusText("La figure a été placée au dessus de toutes les autres!");
                    _frameMain._statusBar.isWork(false);
                } else if (mode == MODE_BELOW) {
                    _frameMain._statusBar.isWork(true);

                    // On change la position de la figure
                    CShape shapeJustBelow = null;
                    for (CShape shape:_canvas.getDisplayList()) {
                        // On récupère la dernière shape en dessous
                        if (shape.hasTag(InkMachine.TAG_SHAPE) && 
                                (shape != selectedShape) &&
                                shape.isBelow(selectedShape)) {
                            shapeJustBelow = shape;
                        }
                    }
                    // MAJ s'il existe une shape au dessus de celle courante
                    if (shapeJustBelow != null)
                        selectedShape.below(shapeJustBelow);

                    _frameMain._statusBar.setStatusText("La figure a été placée en dessous de celle qui la précède!");
                    _frameMain._statusBar.isWork(false);
                } else if (mode == MODE_BELOW_ALL) {
                    _frameMain._statusBar.isWork(true);
                    // On change la position de la figure
                    selectedShape.belowAll();

                    _frameMain._statusBar.setStatusText("La figure a été placée en dessous de toutes les autres!");
                    _frameMain._statusBar.isWork(false);
                }
            }
        };

        // Fonction de sortie
        Transition exit = new Release(BUTTON3, ">> menuOff") {
            public void action() {
                armTimer(100, false);
            }
        };

        // Transition permettant de personnaliser une couleur dans le pieMenu
        Transition custom = new PressOnTag(ColorTag.class, BUTTON1, ">> menuOff") {

            public void action() {
                armTimer(300, true);

                // Change the color of the shape
                color = JColorChooser.showDialog(_canvas, "Choisissez votre couleur", color);

                if (color != null) {
                    if (mode == MODE_FILL_BACKGROUND)       // Remplissage de la figure
                        selectedShape.setFillPaint(color);
                    else if (mode == MODE_FILL_OUTLINED)    // Modification de la couleur des bords de la figure
                        selectedShape.setOutlinePaint(color);

                    // On met à jour la couleur dans le pieMenu
                    getShape().setFillPaint(color);
                    getShape().removeTag((ColorTag) getTag()).addTag(new ColorTag(color));
                }
            }
        };
    };


    /**
     * Etat appelée lorsque l'utilisateur quitte le pieMenu.
     * <br>
     * Il permet de cacher le pieMenu
     */
    public State menuOff = new State() {

        Transition timeout = new TimeOut(">> start") {
            public void action() {
                hideMenu(TAG_PIE_MENU_BACKGROUND);
                hideMenu(TAG_PIE_MENU_OUTLINE);
                hideMenu(TAG_PIE_MENU_CHOICE);
                hideMenu(TAG_PIE_MENU_EDIT);
                hideMenu(TAG_PIE_MENU_POSITION);
                _frameMain._statusBar.isWork(false);
            }
        };
    };


    /**
     * Open the pie menu with the given tag at a given location.
     * @param pt the location of the pie menu center
     * @param tag TAG of the menu to show
     */
    public void showMenu(Point2D pt, String tag) {
        _canvas.getTag(tag).translateTo(pt.getX(), pt.getY());
        _canvas.getTag(tag).setDrawable(true).setPickable(true).aboveAll();
    }

    /**
     * Close the pie menu with the given tag.
     * @param tag TAG of the menu to hide
     */
    public void hideMenu(String tag) {
        _canvas.getTag(tag).setDrawable(false).setPickable(false);
    }

    /**
     * Draw a pie shape.
     * @param _canvas The _canvas in which this pie must be displayed.
     * @param color The darken of the pie.
     * @param x The x-coordinate of the pie's point.
     * @param y The y-coordinate of the pie's point.
     * @param start the starting angle of the pie (in radians)
     * @param extent The extent of the pie (in radians)
     * @param radius The radius of the pie.
     * @return The pie shape.
     */
    public static CPolyLine pie(Canvas _canvas, Color color, double x, double y, double start, double extent, double radius, String tag) {
        CPolyLine itemBackground = _canvas.newPolyLine(x, y);
        itemBackground.lineTo(x + radius, y).arcTo(0, extent, radius, radius).lineTo(x, y);
        itemBackground.setReferencePoint(0, 1).rotateBy(-start).setFillPaint(color).setTransparencyFill(0.4f).setAntialiased(true).setDrawable(false);
        // We attach an instance of ColorTag to each menu item.
        // CNamedTag is a predefined SwingStates tag that
        // associates a string with any _canvas widget shape
        itemBackground.addTag(new ColorTag(color)).addTag(tag);
        return itemBackground;
    }

    /**
     * Dessine le pieMenu des couleurs
     *
     * @param radius    Angle en radian des part du pieMenu
     * @param c         _canvas dans lequel le pieMenu doit être dessiné
     * @return          Le pieMenu sous la forme d'un CShape
     */
    public static CShape pieMenu(int radius, Canvas c, String tag) {
        CEllipse parent = (CEllipse) c.newEllipse(-2, -2, 4, 4).addTag(tag);
        pie(c, new Color(205, 150, 205), 0, 0, 0, Math.PI / 2, radius, tag).setParent(parent);
        pie(c, Color.YELLOW, 0, 0, Math.PI / 2, Math.PI / 2, radius, tag).setParent(parent);
        pie(c, new Color(60, 179, 113), 0, 0, Math.PI, Math.PI / 2, radius, tag).setParent(parent);
        pie(c, new Color(100, 149, 237), 0, 0, 3 * Math.PI / 2, Math.PI / 2, radius, tag).setParent(parent);
        parent.aboveAll();
        return parent;
    }

    /**
     * Méthode créant le menu circulaire
     * @param items Liste des fonctionnalitées offertes par le menu
     * @param tagMenu   TAG des menus
     * @param tagLabels TAG des labels du menu
     * @param tagWholeMenu TAG du menu dans la globalité
     * @return
     */
    private CShape choiceMenuLayout(String[] items, String tagMenu, String tagLabels, String tagWholeMenu) {
        CEllipse parent = _canvas.newEllipse(-5, -5, 10, 10);

        CShape clipCircle = (new CEllipse(-50, -50, 100, 100)).getSubtraction(new CEllipse(-30, -30, 60, 60));
        clipCircle.setFilled(true).setOutlined(false);
        clipCircle.addTag(tagMenu).setParent(parent);

        CPolyLine bgItem;
        CText labelItem;
        double angleStep = 2 * Math.PI / items.length;

        for (int i = 0; i < items.length; i++) {
            bgItem = _canvas.newPolyLine(0, 0).lineTo(50, 0).arcTo(0, -angleStep, 50, 50).close();
            bgItem.addTag(tagMenu).setReferencePoint(0, 0).rotateBy(i * angleStep).setFillPaint(new Color(251, 249, 201)).setOutlinePaint(new Color(130, 195, 240)).setClip(clipCircle);

            labelItem = (CText) _canvas.newText(0, 0, items[i], new Font("verdana", Font.PLAIN, 10)).setPickable(false).addTag(tagMenu).addTag(tagLabels).addTag(new MenuItem(items[i])).setReferencePoint(0.5, 0.5).translateTo(
                    Math.cos((i * angleStep) + angleStep / 2) * 41, Math.sin((i * angleStep) + angleStep / 2) * 41);

            parent.addChild(labelItem).addChild(bgItem);
        }

        parent.addTag(tagMenu).addTag(tagWholeMenu);
        _canvas.getTag(tagLabels).aboveAll();
        parent.aboveAll();

        return parent;
    }

    /**
     * Affiche a la position <i>pt</i> le menu circulaire ayant les tags donnés en arguments
     * @param pt        Position à laquelle le menu doit être affichée
     * @param wholeMenu TAG du menu dans la globalité
     * @param tagMenu   TAG des menus
     * @param tagLabels TAG des labels du menu
     */
    private void showChoiceMenu(Point2D pt, String wholeMenu, String tagMenu, String tagLabels) {
        _canvas.getTag(wholeMenu).translateTo(pt.getX(), pt.getY()).setDrawable(true);
        _canvas.getTag(tagMenu).setDrawable(true).setPickable(true);
        _canvas.getTag(tagLabels).setPickable(true);
        _canvas.getTag(tagMenu).aboveAll();
        _canvas.getTag(tagLabels).aboveAll();
    }
}
